package edu.njucm.herbiv.utils.advice

import edu.njucm.herbiv.utils.JsonUtils
import io.github.oshai.kotlinlogging.KotlinLogging
import jakarta.servlet.http.HttpServletRequest
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.stereotype.Component
import org.springframework.util.ObjectUtils
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.context.request.RequestAttributes
import org.springframework.web.context.request.RequestContextHolder
import java.lang.reflect.Method
import java.lang.reflect.Parameter

private val logger = KotlinLogging.logger { }

/**
 * @Author ZhouHang
 * @Date 24/5/13 013
 */
@Aspect
@Component
class LogAspect {
    @Around("@annotation(edu.njucm.herbiv.utils.annotation.LogRequest)")
    fun logRequest(joinPoint: ProceedingJoinPoint): Any {
        val requestAttributes = RequestContextHolder.getRequestAttributes()
        val request = requestAttributes!!.resolveReference(RequestAttributes.REFERENCE_REQUEST) as HttpServletRequest?

        val start = System.currentTimeMillis()
        val result = joinPoint.proceed()
        val end = System.currentTimeMillis()

        val signature = joinPoint.signature as MethodSignature
//        后期考虑引入 Swagger
//        val method = signature.method
//        if (method.isAnnotationPresent(ApiOperation::class.java)) {
//            val log: ApiOperation = method.getAnnotation(ApiOperation::class.java)
//            webLog.setDescription(log.value())
//        }
        val webLog = WebLog(
            method = request?.method.toString(),
            signature = signature.toShortString(),
            url = request?.requestURL.toString(),
            uri = request?.requestURI.toString(),
            remoteIp = request?.remoteUser.toString(),
            queryString = request?.queryString.toString(),
            spendTime = (end - start).toInt(),
            // post 请求保存返回结果
            result = if (request?.method.equals("POST")) result else null,
            parameters = getParameter(signature.method.parameters, joinPoint.args)
        )
        logger.info { JsonUtils.dumps(webLog) }
        return result
    }

    private fun getParameter(parameters: Array<Parameter>, arguments: Array<Any>): Any? {
        val args = mutableListOf<String>()
        for (i in parameters.indices) {
            // 将 RequestBody 注解修饰的参数视为请求参数
            val requestBody = parameters[i].getAnnotation(RequestBody::class.java)
            if (requestBody != null) {
                args.add(arguments[i].toString())
            }
        }
        return when (args.size) {
            0 -> null
            1 -> args[0]
            else -> args
        }
    }

    @Around("@annotation(edu.njucm.herbiv.utils.annotation.LogExecution)")
    fun logExecutionTime(joinPoint: ProceedingJoinPoint): Any {
        val start = System.currentTimeMillis()
        val signature = joinPoint.signature.toShortString()
        val result = try {
            with(StringBuilder("$signature, ")) {
                appendParameters(joinPoint.args)
                logger.info { toString() }
            }
            joinPoint.proceed()
        } catch (throwable: Throwable) {
            logger.error(throwable) { "*** Exception during executing $signature," }
            throw throwable
        }
        val duration = System.currentTimeMillis() - start
        logger.info { "$signature, returned: '$result', duration: $duration ms" }
        return result
    }

    private fun StringBuilder.appendParameters(args: Array<Any>) {
        append("parameters: [")
        args.forEachIndexed { i, p ->
            append(p.javaClass.simpleName).append("(").append(p.toString()).append(")")
            if (i < args.size - 1) append(", ")
        }
        append("]")
    }
}

